﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.SceneManagement;

namespace Nirvana
{
    public static class AssetManager
    {
        private static Logger logger = LogSystem.GetLogger("AssetManager");
        private static LinkedListNode<Action> assetCallbackList;
        private static Dictionary<string, string> aliasResPathMap = new Dictionary<string, string>();
        private static AssetBundleCache abCache;
        private static TaskManager taskMgr;
        private static AssetBundleLoadTaskMgr abTaskMgr;
        private static AssetBundleManager abMgr;

        private static AssetBundleInfo abInfo;
        private static List<WaitLoadAsset> waitLoadAssetList;

        private static Dictionary<string, string> aa = new Dictionary<string, string>();
        public delegate void UpdateDelegate(float progress, int downloadSpeed, int bytesDownloaded, int contentLength);

        public static AssetSimulator Simulator { get; private set; }

        static AssetManager()
        {
            abCache = new AssetBundleCache();
            taskMgr = new TaskManager();
            abTaskMgr = new AssetBundleLoadTaskMgr(abCache, taskMgr);
            abMgr = new AssetBundleManager(abTaskMgr);
            abInfo = new AssetBundleInfo();
            waitLoadAssetList = new List<WaitLoadAsset>();
            Simulator = new AssetSimulator();

        }

        public static event Action<string> DownloadStartEvent
        {
            add
            {
                taskMgr.AddStartEvent(value);
            }
            remove
            {
                taskMgr.RemoveStartEvent(value);
            }
        }

        public static event Action<string> DownloadFinishEvent
        {
            add
            {
                taskMgr.AddFinishEvent(value);
            }
            remove
            {
                taskMgr.RemoveFinishEvent(value);
            }
        }

        public static string CachePath
        {
            get { return abCache.GetAssetCachePath(); }
        }

        public static string DownloadingURL
        {
            get { return abTaskMgr.DownUrl; }
            set { abTaskMgr.DownUrl = value; }
        }

        public static string AssetVersion
        {
            get { return abTaskMgr.Version; }
            set { abTaskMgr.Version = value; }
        }

        public static bool IgnoreHashCheck
        {
            get { return abTaskMgr.IngoreHash; }
            set { abTaskMgr.IngoreHash = value; }
        }

        public static string[] ActiveVariants
        {
            get { return abMgr.Variants; }
            set { abMgr.Variants = value; }
        }

        public static AssetBundleManifest Mainfest
        {
            get { return abMgr.Manifest; }
        }

        public static bool HashManifest
        {
            get
            {
                return
                    Simulator.SimulateAssetBundle ||
                    Mainfest != null;
            }
        }

        public static void DrawAssetBundles()
        {
            abMgr.DrawAssetBundles();
        }

        public static void ClearCache()
        {
            abCache.DeleteAllCache();
        }

        public static IEnumerator Dispose()
        {
            abTaskMgr.SetStartLoadingState();
            abMgr.Manifest = null;
            waitLoadAssetList.Clear();

            return new WaitUntil(() =>
            {
                if (!abMgr.IsLoadSuccess())
                {
                    if (assetCallbackList != null)
                    {
                        Scheduler.RemoveFrameListener(assetCallbackList);
                        assetCallbackList = null;
                    }
                    abMgr.Release();
                    return true;
                }
                return false;
            });
        }

        /// <summary>
        /// 获取版本文件信息
        /// </summary>
        /// <returns></returns>
        public static string LoadVersion()
        {
            return abCache.GetVersion();
        }

        public static void SaveVersion(string version)
        {
            abCache.SetVersion(version);
        }

        public static WaitLoadAsset LoadLocalManifest(string assetpath)
        {
            assetCallbackList = Scheduler.AddFrameListener(OnLoadFinish);

            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.WaitLoadObject();
            }
            if (!abMgr.LoadBundleAsync(assetpath))
            {
                logger.LogError("Can not load local manifest:{0}", assetpath);
                return null;
            }
            var loadmanifest = new WaitLoadAssetManifest(abMgr, assetpath, "AssetBundleManifest", typeof(AssetBundleManifest));
            waitLoadAssetList.Add(loadmanifest);
            return loadmanifest;

        }

        public static WaitLoadAsset LoadRemoteManifest(string assetpath)
        {

            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.WaitLoadObject();
            }

            if (!abMgr.LoadBundleAsync(assetpath))
            {
                logger.LogError("Can not load local manifest:{0}", assetpath);
                return null;
            }
            var loadmanifest = new WaitLoadAssetManifest(abMgr, assetpath, "AssetBundleManifest", typeof(AssetBundleManifest));
            waitLoadAssetList.Add(loadmanifest);
            return loadmanifest;

        }

        public static bool IsExistInStreamingAsset(string filePath)
        {
            return abCache.IsExistInStreamingAsset(filePath);
        }

        public static bool IsVersionCached(string bundleName)
        {
            Hash128 hash = Mainfest.GetAssetBundleHash(bundleName);
            return abCache.IsValidateCache(bundleName, hash);
        }

        public static bool IsVersionCached(string bundleName, Hash128 hash)
        {
            return abCache.IsValidateCache(bundleName, hash);
        }

        public static WaitLoadFileInfo LoadFileInfo()
        {
            Assert.IsFalse(string.IsNullOrEmpty(DownloadingURL));
            string path = DownloadingURL + "/file_info.txt";
            return abInfo.Load(path);
        }

        public static void LoadAliasResPathMap()
        {
            string path = Path.Combine(Application.streamingAssetsPath, "AssetBundle/res_alias_path_map.txt");
            if (!File.Exists(path)) return;
            Debug.LogFormat("[LoadAliasResPathMap] start load {0}", path);
            string[] txts = File.ReadAllLines(path);
            foreach (var t in txts)
            {
                string[] chars = t.Split(' ');
                if (chars.Length != 2) Debug.LogErrorFormat("[LoadAliasResPathMap] error {0}", t);
                else
                {
                    string key = "AssetBundle/" + chars[0];
                    string value = "AssetBundle/" + chars[1];
                    aliasResPathMap[key] = value;
                }
            }
            Debug.LogErrorFormat("[LoadAliasResPathMap] load succ, count: {0}", aliasResPathMap.Count);
        }

        public static void SetAliasResPathMap(Dictionary<string, string> map)
        {
            aliasResPathMap = map;
        }

        public static string GetAliasResPath(string path)
        {
            if (aliasResPathMap.Count == 0) return path;
            string aliaspath = string.Empty;
            path = path.Replace("\\", "/");
            if (aliasResPathMap.TryGetValue(path, out aliaspath))
            {
                return path;
            }
            return aliaspath;
        }

        public static WaitLoadAssetBundle UpdateBundle(string bundlename)
        {
            Hash128 hash = Mainfest.GetAssetBundleHash(bundlename);
            return abTaskMgr.UpdateBundle(bundlename, hash);
        }

        public static WaitLoadAssetBundle UpdateBundle(string bundlename, Hash128 hash)
        {
            return abTaskMgr.UpdateBundle(bundlename, hash);
        }

        public static AssetBundle LoadBundleLocal(string bundlename)
        {
            abMgr.AddHash(bundlename, "[Asset], from AssetManager.LoadBundleLocal method");
            return abMgr.GetAssetBundle(bundlename);
        }

        public static UnityEngine.Object LoadObjectLocal(AssetID assetID, Type assetType)
        {
            return LoadObjectLocal(assetID.BundleName, assetID.AssetName, assetType);
        }

        public static UnityEngine.Object LoadObjectLocal(string assetBundleName, string assetName, Type assetType)
        {
#if UNITY_EDITOR
            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.LoadObject(assetBundleName, assetName, assetType);
            }
#endif
            var ab = abMgr.GetAssetBundle(assetBundleName);
            if (ab == null)
            {
                Debug.LogErrorFormat("[LoadObjectLocal] load assetbundle fail {0}", assetBundleName);
                return null;
            }

            abMgr.AddHash(assetBundleName, "[Asset] from AssetManager.LoadObjectLocal method. assetName = " + assetName);
            return ab.LoadAsset(assetName, assetType);
        }

        public static WaitLoadObject LoadObject(AssetID assetID, Type assetType)
        {
            return LoadObject(assetID.BundleName, assetID.AssetName, assetType);
        }

        public static WaitLoadObject LoadObject(string assetBundleName, string assetName, Type assetType)
        {
#if UNITY_EDITOR
            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.LoadAsync(assetBundleName, assetName, assetType);
            }
#endif

            if (LogSystem.IsLogAct(LogActType.LoadAsset))
            {
                logger.LogInfo(string.Format("LoadObject {0} {1}", assetBundleName, assetName));
            }

            if (abMgr.IsGetAssetBundle(assetBundleName, false))
            {
                abMgr.AddHash(assetBundleName, "[Asset] from AssetManager.LoadObject method. assetName = " + assetName);
                WaitLoadObjectAsync wait = new WaitLoadObjectAsync(abMgr, assetBundleName, assetName, assetType);
                waitLoadAssetList.Add(wait);
                return wait;
            }

            return new WaitLoadObjectAsync(string.Format("Load asset bundle {0}:{1} failed.", assetBundleName, assetName));
        }

        public static WaitLoadObject LoadObjectSync(AssetID assetID, Type assetType)
        {
            return LoadObjectSync(assetID.BundleName, assetID.AssetName, assetType);
        }


        public static WaitLoadObject LoadObjectSync(string assetBundleName, string assetName, Type assetType)
        {
#if UNITY_EDITOR
            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.Load(assetBundleName, assetName, assetType);
            }
#endif
            if (LogSystem.IsLogAct(LogActType.LoadAsset))
            {
                logger.LogInfo(string.Format("LoadObject {0} {1}", assetBundleName, assetName));
            }

            if (abMgr.IsGetAssetBundle(assetBundleName, true))
            {
                abMgr.AddHash(assetBundleName, "[Asset] from AssetManager.LoadObjectSync method. assetName = " + assetName);
                WaitLoadObjectSync wait = new WaitLoadObjectSync(abMgr, assetBundleName, assetName, assetType);
                waitLoadAssetList.Add(wait);
                return wait;
            }

            return new WaitLoadObjectAsync(string.Format("Load asset bundle {0}:{1} failed.", assetBundleName, assetName));
        }

        public static WaitLoadAssetProgress LoadLevel(AssetID assetID, LoadSceneMode loadMode)
        {
            return LoadLevel(assetID.BundleName, assetID.AssetName, loadMode);
        }

        public static WaitLoadAssetProgress LoadLevel(string assetBundleName, string sceneName, LoadSceneMode loadMode)
        {
#if UNITY_EDITOR
            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.LoadSceneAsync(assetBundleName, sceneName, loadMode);
            }
#endif
            if (abMgr.IsGetAssetBundle(assetBundleName, false))
            {
                abMgr.AddHash(assetBundleName, "[Asset] from [AssetManager.LoadLevel] method. assetName = " + sceneName);
                WaitLoadSceneAsync wait = new WaitLoadSceneAsync(abMgr, assetBundleName, sceneName, loadMode);
                waitLoadAssetList.Add(wait);
                return wait;
            }
            return new WaitLoadSceneAsync("Load Level {0}:{1} failed.", new object[]
            {
                assetBundleName,
                sceneName
            });
        }

        public static WaitLoadAssetProgress LoadLevelSync(AssetID assetID, LoadSceneMode loadMode)
        {
            return LoadLevelSync(assetID.BundleName, assetID.AssetName, loadMode);
        }

        public static WaitLoadAssetProgress LoadLevelSync(string assetBundleName, string sceneName, LoadSceneMode loadMode)
        {
#if UNITY_EDITOR
            if (Simulator.SimulateAssetBundle)
            {
                return Simulator.LoadScene(assetBundleName, sceneName, loadMode);
            }
#endif
            if (abMgr.IsGetAssetBundle(assetBundleName, true))
            {
                abMgr.AddHash(assetBundleName, "[Asset] from [AssetManager.LoadLevelSync] method. assetName = " + sceneName);
                WaitLoadScene wait = new WaitLoadScene(abMgr, assetBundleName, sceneName, loadMode);
                waitLoadAssetList.Add(wait);
                return wait;
            }
            return new WaitLoadSceneAsync("Load Level {0}:{1} failed.", new object[]
            {
                assetBundleName,
                sceneName
            });
        }
        public static string[] GetAssetsNamesInBundle(string assetBundleName, bool isFullName = false)
        {

            if (Simulator.SimulateAssetBundle)
            {
                return new string[0];
            }

            AssetBundle assetBundle = LoadBundleLocal(assetBundleName);
            if (null == assetBundle)
            {
                return new string[0];
            }
            string[] allAssetNames = assetBundle.GetAllAssetNames();
            if (!isFullName)
            {
                for (int i = 0; i < allAssetNames.Length; i++)
                {
                    int num = allAssetNames[i].LastIndexOf("/");
                    if (num >= 0)
                    {
                        allAssetNames[i] = allAssetNames[i].Substring(num + 1);
                    }
                }
            }
            return allAssetNames;
        }
        public static string[] GetDependBundles(string assetBundleName)
        {

            if (Simulator.SimulateAssetBundle)
            {
                return new string[0];
            }

            return Mainfest.GetAllDependencies(assetBundleName);
        }

        public static string[] GetBundlesWithoutCached(string assetBundleName)
        {

            if (Simulator.SimulateAssetBundle)
            {
                return new string[0];
            }

            return abMgr.GetAssets(assetBundleName);
        }

        public static bool UnloadAsseBundle(string assetBundle)
        {
            return abMgr.IsGetAssetBundle(assetBundle);
        }

        public static void UnloadAllUnUsedAssetBundle()
        {
            abMgr.RemoveAll();
        }

        private static void ExcuteTask()
        {
            abTaskMgr.ExcuteTask();
            AssetManager.abMgr.AddBundleItem();
            waitLoadAssetList.RemoveAll(p => !p.Load());
        }

        public static void SetAssetBundleLoadMaxCount(int loadCount, int downCount)
        {
            abMgr.MaxCount = loadCount;
            AssetBundleLoadTask.MaxDownCount(downCount);
        }

        public static void SetDownloadMaxWorkerCount(int workerCount)
        {
            taskMgr.MaxTaskCount = workerCount;
        }

        public static void ClearAllUnusingAssetBundles(bool isOutTxt)
        {
            Singleton<GameObjectPool>.Instance.ClearAllUnused();
            Singleton<PrefabPool>.Instance.ClearAllUnused();
            Singleton<ScriptablePool>.Instance.ClearAllUnused();
            Singleton<SpritePool>.Instance.ClearAllUnused();
            UnloadAllUnUsedAssetBundle();
            string contents = abMgr.GetAllBundleInfo(isOutTxt);
            string path = isOutTxt ? string.Format("{0}/../Log/UsingAssetBundlesDetail.txt", Application.dataPath) : string.Format("{0}/../Log/UsingAssetBundles.txt", Application.dataPath);
            File.WriteAllText(path, contents);
        }

        public static void LoadRemoteManifest(string manifestAssetBundleName, Action<string> complete)
        {
            Scheduler.RunCoroutine(WaitLoadAsset(manifestAssetBundleName, complete));
        }

        public static void LoadFileInfo(Action<string, AssetBundleInfo> complete)
        {
            Scheduler.RunCoroutine(WaitLoadAsset(complete));
        }

        public static void UpdateBundle(string bundleName, UpdateDelegate update, Action<string> complete)
        {
            Scheduler.RunCoroutine(WaitLoadAsset(bundleName, update, complete));
        }
        public static void UpdateBundle(string bundleName, Hash128 hash, AssetManager.UpdateDelegate update, Action<string> complete)
        {
            Scheduler.RunCoroutine(AssetManager.WaitLoadAsset(bundleName, hash, update, complete));
        }

        public static void LoadObject(AssetID assetID, Type assetType, Action<UnityEngine.Object> complete)
        {
            LoadObject(assetID.BundleName, assetID.AssetName, assetType, complete);
        }


        public static void LoadObject(string assetBundleName, string assetName, Type assetType, Action<UnityEngine.Object> complete)
        {
            if (assetType == typeof(GameObject))
            {
                Debug.LogError("Please Do not use the method to load GameObject");
                return;
            }
            Scheduler.RunCoroutine(WaitLoadAsset(assetBundleName, assetName, assetType, complete));
        }

        public static void LoadObjectSync(AssetID assetID, Type assetType, Action<UnityEngine.Object> complete)
        {
            LoadObjectSync(assetID.BundleName, assetID.AssetName, assetType, complete);
        }

        public static void LoadObjectSync(string assetBundleName, string assetName, Type assetType, Action<UnityEngine.Object> complete)
        {
            if (assetType == typeof(GameObject))
            {
                Debug.LogError("Please Do not use the method to load GameObject");
                return;
            }
            Scheduler.RunCoroutine(WaitLoadAssetSync(assetBundleName, assetName, assetType, complete));
        }

        public static void LoadLevel(AssetID assetID, LoadSceneMode loadMode, Action complete)
        {
            LoadLevel(assetID.BundleName, assetID.AssetName, loadMode, complete, null);
        }
        public static void LoadLevel(string assetBundleName, string levelName, LoadSceneMode loadMode, Action complete, Action<float> progress = null)
        {
            Scheduler.RunCoroutine(WaitLoadLevel(assetBundleName, levelName, loadMode, complete, progress));
        }
        public static void LoadLevelSync(AssetID assetID, LoadSceneMode loadMode, Action complete)
        {
            LoadLevelSync(assetID.BundleName, assetID.AssetName, loadMode, complete);
        }
        public static void LoadLevelSync(string assetBundleName, string levelName, LoadSceneMode loadMode, Action complete)
        {
            Scheduler.RunCoroutine(WaitLoadLevel(assetBundleName, levelName, loadMode, complete));
        }





        private static IEnumerator WaitLoadAsset(Action<string, AssetBundleInfo> cb)
        {
            WaitLoadFileInfo waitLoadFileInfo = LoadFileInfo();
            yield return waitLoadFileInfo;
            cb(waitLoadFileInfo.Error, waitLoadFileInfo.FileInfo);
            yield break;
        }

        private static IEnumerator WaitLoadAsset(string manifest, Action<string> cb)
        {
            WaitLoadAsset waitLoadAsset = LoadRemoteManifest(manifest);
            yield return waitLoadAsset;
            cb(waitLoadAsset.Error);
            yield break;
        }

        private static IEnumerator WaitLoadAsset(string bundleName, UpdateDelegate update, Action<string> cb)
        {
            WaitLoadAssetBundle waitUpdateAssetBundle = UpdateBundle(bundleName);
            if (!string.IsNullOrEmpty(waitUpdateAssetBundle.Error))
            {
                cb(waitUpdateAssetBundle.Error);
                yield break;
            }
            LinkedListNode<Action> handle = Scheduler.AddFrameListener(ExcuteTask);
            yield return waitUpdateAssetBundle;
            Scheduler.RemoveFrameListener(handle);
            cb(waitUpdateAssetBundle.Error);
            yield break;
        }

        private static IEnumerator WaitLoadAsset(string bundleName, Hash128 hash, UpdateDelegate update, Action<string> cb)
        {
            WaitLoadAssetBundle waitUpdateAssetBundle = UpdateBundle(bundleName, hash);
            if (!string.IsNullOrEmpty(waitUpdateAssetBundle.Error))
            {
                cb(waitUpdateAssetBundle.Error);
                yield break;
            }
            LinkedListNode<Action> handle = Scheduler.AddFrameListener(ExcuteTask);
            yield return waitUpdateAssetBundle;
            Scheduler.RemoveFrameListener(handle);
            cb(waitUpdateAssetBundle.Error);
            yield break;
        }

        private static IEnumerator WaitLoadAsset(string assetBundleName, string assetName, Type assetType, Action<UnityEngine.Object> cb)
        {
            WaitLoadObject waitLoadObject = LoadObject(assetBundleName, assetName, assetType);
            yield return waitLoadObject;
            if (!string.IsNullOrEmpty(waitLoadObject.Error))
            {
                logger.LogError(waitLoadObject.Error);
            }
            cb(waitLoadObject.GetObject());
            yield break;
        }

        private static IEnumerator WaitLoadAssetSync(string assetBundleName, string assetName, Type assetType, Action<UnityEngine.Object> cb)
        {
            WaitLoadObject waitLoadObject = LoadObjectSync(assetBundleName, assetName, assetType);
            yield return waitLoadObject;
            if (!string.IsNullOrEmpty(waitLoadObject.Error))
            {
                logger.LogError(waitLoadObject.Error);
            }
            cb(waitLoadObject.GetObject());
            yield break;
        }

        private static IEnumerator WaitLoadLevel(string assetBundleName, string levelName, LoadSceneMode loadMode, Action cb, Action<float> progress)
        {
            WaitLoadAssetProgress waitLoadLevel = LoadLevel(assetBundleName, levelName, loadMode);
            if (progress != null)
            {
                LinkedListNode<Action> handle = Scheduler.AddFrameListener(ExcuteTask);
                yield return waitLoadLevel;
                Scheduler.RemoveFrameListener(handle);
            }
            else
            {
                yield return waitLoadLevel;
            }
            if (!string.IsNullOrEmpty(waitLoadLevel.Error))
            {
                logger.LogError(waitLoadLevel.Error);
            }
            cb();
            yield break;
        }

        private static IEnumerator WaitLoadLevel(string assetBundleName, string levelName, LoadSceneMode loadMode, Action cb)
        {
            WaitLoadAssetProgress waitLoadLevel = LoadLevelSync(assetBundleName, levelName, loadMode);
            yield return waitLoadLevel;
            if (!string.IsNullOrEmpty(waitLoadLevel.Error))
            {
                logger.LogError(waitLoadLevel.Error);
            }
            cb();
            yield break;
        }


        private static void OnLoadFinish()
        {
            abTaskMgr.ExcuteTask();
            abMgr.AddBundleItem();
            waitLoadAssetList.RemoveAll(p => !p.Load());
        }

    }
}

